////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//         This file is distributed under the BSD 2-Clause open source license. See Licenses/License.txt for details.         //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNScripting/inc/WNArrayAllocation.h"
#include "WNScripting/inc/WNTypeNode.h"
#include "WNScripting/inc/WNCodeModule.h"
#include "WNScripting/inc/WNTypeManager.h"
#include "WNScripting/inc/WNNodeHelpers.h"

using namespace WNScripting;

eWNTypeError AllocateArray(WNCodeModule& _module, llvm::Value*& _retVal, const WNFunctionDefinition* _def, WNScriptType _type, WNScriptLinkedList<WNExpression>::WNScriptLinkedListNode* _initializer, WNLogging::WNLog& _compilationLog)
{
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_module.GetBuilder());
        
    _retVal = WN_NULL;
    eWNTypeError err = eWNOK;
    if(!_initializer) {
        WNScriptType nullType;
        if(eWNOK != (err = GenerateNullAllocation(_module, _def, _retVal, nullType))) {
            return(err);
        }
    }
    if(!_type) {
        //Last level
        return(eWNError);
    }

    llvm::Value* allocLocation = WN_NULL;
    const GenerateAllocation* alloc = _module.GetTypeManager().GetAllocationOperation(_type);
    if(!alloc) {
        return(eWNCannotCreateType);
    }
    if(eWNOK != (err = alloc->Execute(_module, _type, "", false, allocLocation))) {
        return(err);
    }

#ifdef _WN_64_BIT
    llvm::Type* ptrType = llvm::IntegerType::getInt64Ty(llvm::getGlobalContext());
    const WN_SIZE_T ptrSize = 8;
#else
    llvm::Type* ptrType = llvm::IntegerType::getInt32Ty(llvm::getGlobalContext());
    const WN_SIZE_T ptrSize = 4;
#endif
    WNScriptType sizeTType = 0;
    WNScriptType intType = 0;
    if(eWNOK != (err = _module.GetTypeManager().GetTypeByName("-SizeT", sizeTType)) ||
        eWNOK != (err = _module.GetTypeManager().GetTypeByName("Int", intType))) {
        return(err);
    }
    std::vector<WNScriptType> mallocTypes;
    mallocTypes.push_back(sizeTType);
    WNFunctionDefinition* def = _module.GetFunctionDefinition("Malloc", mallocTypes);
    if(!def) {
        return(eWNPlatformError);
    }

    if(eWNOK != (err = _initializer->value->GenerateCode(_module, _def, _compilationLog))) {
        return(err);
    }

    llvm::Value* sizeVal = _initializer->value->GetValue();
    if(_initializer->value->GetType() != intType) {
        const GenerateCastingOperation* cast = _module.GetTypeManager().GetCastingOperation(_initializer->value->GetType(), intType);
        if(!cast) {
            return(eWNInvalidCast);
        }
        if(eWNOK != (err = cast->Execute(builder, sizeVal, sizeVal))) {
            return(err);
        }
    }

    llvm::Value* val = llvm::ConstantInt::get(sizeTType->mLLVMType, _module.GetExecutionEngine()->getDataLayout()->getTypeAllocSize(_type->mLLVMType->getContainedType(0)));
        
    llvm::Value* v = builder->CreateCall(def->mFunction, val, "");
    v = builder->CreatePointerCast(v, allocLocation->getType()->getContainedType(0));

    WN_UINT64 allocSize = _module.GetExecutionEngine()->getDataLayout()->getTypeAllocSize(_type->mArrayType->mLLVMType);
    llvm::Value* allocationSize = builder->CreateMul(sizeVal, llvm::ConstantInt::get(intType->mLLVMType, allocSize));
    allocationSize = builder->CreateAdd(allocationSize, llvm::ConstantInt::get(intType->mLLVMType, ptrSize));
        
    llvm::Value* actualArray = builder->CreateCall(def->mFunction, allocationSize, "");
    actualArray = builder->CreatePointerCast(actualArray, _type->mArrayType->mLLVMType->getPointerTo(0));

    std::vector<llvm::Value*> GepArray;
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 1));
    builder->CreateStore(actualArray, builder->CreateGEP(v, GepArray));

    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 2));
    builder->CreateStore(llvm::ConstantInt::get(ptrType, 0), builder->CreateGEP(v, GepArray));

    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));

    builder->CreateStore(builder->CreateIntToPtr(llvm::ConstantInt::get(sizeTType->mLLVMType, 0), llvm::IntegerType::getInt32PtrTy(llvm::getGlobalContext())), builder->CreateGEP(v, GepArray));

    llvm::Value* storeSizeLocation = builder->CreatePointerCast(actualArray, ptrType->getPointerTo(0));
    builder->CreateStore(builder->CreateIntCast(sizeVal, ptrType, false), storeSizeLocation);
    if(!(_type->mArrayType->mLLVMStructType || _type->mArrayType->mArrayType )) {
        _retVal = v;
        return(eWNOK);
    }

    llvm::Value* dataPtr = builder->CreatePtrToInt(actualArray, ptrType);
    dataPtr = builder->CreateAdd(dataPtr, llvm::ConstantInt::get(ptrType, ptrSize));
    dataPtr = builder->CreateIntToPtr(dataPtr, _type->mArrayType->mLLVMType->getPointerTo(0));

    llvm::Value* counterLoc = builder->CreateAlloca(ptrType);
    builder->CreateStore(llvm::ConstantInt::get(ptrType, 0), counterLoc);
    llvm::BasicBlock* counterBlock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", _def->mFunction);
    llvm::BasicBlock* bodyBlock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", _def->mFunction);
    llvm::BasicBlock* endBlock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", _def->mFunction);
    builder->CreateBr(counterBlock);
    builder->SetInsertPoint(counterBlock);
    llvm::Value* cond = builder->CreateICmpUGE(builder->CreateLoad(counterLoc), sizeVal);
    builder->CreateCondBr(cond, endBlock, bodyBlock);
    builder->SetInsertPoint(bodyBlock);

    llvm::Value* gepLocation = builder->CreateGEP(dataPtr, builder->CreateLoad(counterLoc));
    if(_type->mArrayType->mLLVMStructType) {
        llvm::Value* structCreation;
        if(eWNOK != (err = AllocateStruct(_module, structCreation, _type->mArrayType, _def, _compilationLog))) {
            return(err);
        }
        const GenerateAssignment* assOp =_module.GetTypeManager().GetAssignmentOperation(_type->mArrayType, AT_EQ);
        if(eWNOK != (err = assOp->Execute(_module, true, _type->mArrayType, structCreation, gepLocation, _def, _compilationLog))) {
            return(err);
        }
    } else if(_type->mArrayType->mArrayType) {
        llvm::Value* arrayCreation;
        if(eWNOK != (err = AllocateArray(_module, arrayCreation, _def, _type->mArrayType, _initializer->next, _compilationLog))) {
            return(err);
        }
        const GenerateAssignment* assOp =_module.GetTypeManager().GetAssignmentOperation(_type->mArrayType, AT_EQ);
        if(eWNOK != (err = assOp->Execute(_module, true, _type->mArrayType, arrayCreation, gepLocation, _def, _compilationLog))) {
            return(err);
        }
    }
    builder->CreateStore(builder->CreateAdd(builder->CreateLoad(counterLoc), llvm::ConstantInt::get(ptrType, 1)), counterLoc);
    builder->CreateBr(counterBlock);
    builder->SetInsertPoint(endBlock);
    _retVal = v;
    return(eWNOK);
}

WNArrayAllocation::WNArrayAllocation() :
    mLevels(0),
    mType(WN_NULL) {
}

WNArrayAllocation::~WNArrayAllocation() {
    if(mType) {
        WN_DELETE(mType);
    }
    for(WNScriptLinkedList<WNExpression>::WNScriptLinkedListNode* i = mArrayInitializers.first; i != WN_NULL; i = i->next) {
        WN_DELETE(i->value);
    }
}   

WN_VOID WNArrayAllocation::SetType(WNTypeNode* _typeNode) {
    mType = _typeNode;
}

WN_VOID WNArrayAllocation::AddExpression(WNExpression* _expr) {
    mArrayInitializers.PushBack(_expr);
    mLevels++;
}

WN_VOID WNArrayAllocation::AddLevel() {
    mLevels++;
}

eWNTypeError WNArrayAllocation::GenerateCode(WNCodeModule& _module, const WNFunctionDefinition* _def, WNLogging::WNLog& _compilationLog) {
    eWNTypeError err = eWNOK;
    
    if(eWNOK != (err = mType->GetType(_module, mScriptType))){
        _compilationLog.Log(WNLogging::eError, 0, "Cannot find type ");
        LogLine(_compilationLog, WNLogging::eError);
        return(err);
    }
    for(WN_SIZE_T i = 0; i < mLevels; ++i){
        if(eWNOK != (err = _module.GetTypeManager().GetArrayOf(mScriptType, mScriptType))) {
            _compilationLog.Log(WNLogging::eError, 0, "Cannot create array of ", mScriptType->mName);
            LogLine(_compilationLog, WNLogging::eError);
            return(err);
        }
    }
    if(eWNOK != (err = AllocateArray(_module, mValue, _def, mScriptType, mArrayInitializers.first, _compilationLog))) {
        _compilationLog.Log(WNLogging::eError, 0, "Cannot allocate array", mScriptType->mName);
        LogLine(_compilationLog, WNLogging::eError);
        return(err);
    }
    return(eWNOK);            
}
